using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Security;
using System.Threading;
using System.Configuration;
using NBusiness.Utilities;
using System.ComponentModel;
using System.Collections;
using System.Collections.ObjectModel;
using NBusiness.Properties;
using NBusiness.Query;

namespace NBusiness.Data
{
    [Serializable]
    public abstract class EntityBaseCollection<T> :
        IEntityCollection,
        IEnumerable<T>
        where T : EntityBase, new()
    {
        #region Constructors
        protected EntityBaseCollection(DbConnection cn, IQuery query, params Parameter[] parameters)
            : this(cn, new QueryCriteria(query, parameters))
        { }
        protected EntityBaseCollection(IQuery query, params Parameter[] parameters)
            : this(new QueryCriteria(query, parameters))
        { }
        protected EntityBaseCollection(CriteriaBase criteria)
            : this()
        {
            string connectionStringName = (string)Settings.Instance.GetSetting(SettingsType.ConnectionStringName, this.GetType());
            string connstr = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
            using (DbConnection cn = ((IDatabaseProvider)Settings.Instance.GetSetting(SettingsType.Provider, this.GetType())).GetConnection(connstr))
            {
                cn.Open();
                try
                {
                    Fetch(cn, criteria);
                }
                catch (Exception innerEx)
                {
                    try
                    {
                        OnError(innerEx);
                    }
                    catch (Exception outerEx)
                    {
                        throw new EntityErrorException("Failed to handle Error", innerEx, outerEx);
                    }

                    throw innerEx;
                }
            }
        }
        protected EntityBaseCollection(DbConnection cn, CriteriaBase criteria)
            : this()
        {
            try
            {
                Fetch(cn, criteria);
            }
            catch (Exception innerEx)
            {
                try
                {
                    OnError(innerEx);
                }
                catch (Exception outerEx)
                {
                    throw new EntityErrorException("Failed to handle Error", innerEx, outerEx);
                }

                throw innerEx;
            }
        }
        public EntityBaseCollection()
        {
            try
            {
                //Forces static constructors to be called and rules
                //to be initialized.
                T t = new T();
                Initialize();
            }
            catch (Exception innerEx)
            {
                try
                {
                    OnError(innerEx);
                }
                catch (Exception outerEx)
                {
                    throw new EntityErrorException("Failed to handle Error", innerEx, outerEx);
                }

                throw innerEx;
            }
        }
        #endregion

        #region Initialize
        /// <summary>
        /// Implement in partial class to allow user code to attach events and implement custom code.
        /// </summary>
        public virtual void Initialize()
        {
        }
        #endregion

        #region Authorization Rules
        public bool IsValid
        {
            get
            {
                bool valid = true;
                foreach (T c in this)
                {
                    if (!c.IsValid)
                    {
                        valid = false;
                        break;
                    }
                }
                return valid;
            }
        }
        /// <summary>
        /// Check to see if this user can fetch this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanFetch()
        {
            return EntityBase.AuthorizationRules.CanFetch(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Insert this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanInsert()
        {
            return EntityBase.AuthorizationRules.CanInsert(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Update this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanUpdate()
        {
            return EntityBase.AuthorizationRules.CanUpdate(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Delete this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanDelete()
        {
            return EntityBase.AuthorizationRules.CanDelete(typeof(T), Thread.CurrentPrincipal);
        }
        #endregion

        #region Fetch
        protected virtual void Fetch(DbConnection cn, CriteriaBase criteria)
        {
            if (!CanFetch()) throw new SecurityException("Not Authorized to Fetch");
            if (!OnFetching(cn, criteria))
            {
                FetchEntityCollection(cn, criteria);
                OnFetched(cn);
            }
        }
        protected abstract void FetchEntityCollection(DbConnection cn, CriteriaBase criteria);
        /// <summary>
        /// Implment this event to handle pre fetch conditions. Setting the argument Cancel property
        /// to true will prevent the fetch from occurring and the Fetched event will not fire.
        /// </summary>
        public event EventHandler<FetchEventArgs> Fetching;
        protected virtual bool OnFetching(DbConnection cn, CriteriaBase criteria)
        {
            bool cancel = false;
            if (Fetching != null)
            {
                FetchEventArgs fetchArgs = new FetchEventArgs(cn, criteria);
                Fetching(this, fetchArgs);
                cancel = fetchArgs.Cancel;
            }
            return cancel;
        }
        /// <summary>
        /// Implement this event to add extra behavior during a fetch of this Entity on the same
        /// connection.
        /// </summary>
        public event EventHandler<EventArgs<DbConnection>> Fetched;
        protected virtual void OnFetched(DbConnection cn)
        {
            if (Fetched != null) Fetched(this, new EventArgs<DbConnection>(cn));
        }
        #endregion

        #region Load
        public virtual void Load(object sender, LoadEventArgs e)
        {
            OnLoading(e);
            LoadEntityCollection(this, e);
            OnLoaded(e);
        }

        protected abstract void LoadEntityCollection(object sender, LoadEventArgs e);

        public event EventHandler<LoadEventArgs> Loading;
        protected virtual void OnLoading(LoadEventArgs e)
        {
            if (Loading != null) Loading(this, e);
        }

        public event EventHandler<LoadEventArgs> Loaded;
        protected virtual void OnLoaded(LoadEventArgs e)
        {
            if (Loaded != null) Loaded(this, e);
        }

        #endregion

        #region Member properties and fields
        private List<T> _deletedList = new List<T>();
        private List<T> _items = new List<T>();
        #endregion

        #region ICollection interfaces
        #region ICollection<T> Members

        /// <summary>
        /// Override this to add behavior to how the item is added. By default _Items.Add(item) is called.
        /// You may wish to change some fields as an item is added, for example.
        /// </summary>
        /// <param name="item"></param>
        public virtual void Add(T item)
        {
            _items.Add(item);
            OnAdded(item);
        }

        public event EventHandler<EventArgs<T>> Added;
        protected virtual void OnAdded(T item)
        {
            if (Added != null) Added(this, new EventArgs<T>(item));
        }

        public virtual void Clear()
        {
            foreach (T c in _items)
            {
                DeleteItem(c);
                OnRemoved(c);
                _deletedList.Add(c);
            }
            _items.Clear();
        }

        public bool Contains(T item)
        {
            return _items.Contains(item);
        }

        public virtual void CopyTo(T[] array, int arrayIndex)
        {
            _items.CopyTo(array, arrayIndex);
        }

        public virtual int Count
        {
            get { return _items.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }
        public T this[int index]
        {
            get { return _items[index]; }
            set
            {
                if (_items[index] != null)
                {
                    T item = _items[index];
                    _deletedList.Add(item);
                    DeleteItem(item);
                    OnRemoved(item);
                }
                _items[index] = value;
                OnAdded(value);
            }
        }
        public bool Remove(T item)
        {
            bool removed = false;
            if (_items.Contains(item))
            {
                _items.Remove(item);
                _deletedList.Add(item);
                DeleteItem(item);
                OnRemoved(item);

                removed = true;
            }
            return removed;
        }
        /// <summary>
        /// This method is called when a
        /// </summary>
        public event EventHandler<EventArgs<T>> Removed;
        protected virtual void OnRemoved(T item)
        {
            if (Removed != null) Removed(this, new EventArgs<T>(item));
        }
        /// <summary>
        /// Override this method to alter how an item is to be deleted. By defualt item.Delete() is called.
        /// Sometimes you may wish to simply alter a field rather than actually delete the object.
        /// </summary>
        /// <param name="item">The item to delete</param>
        public virtual void DeleteItem(T item)
        {
            item.Delete();
        }
        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return ((IEnumerable<T>)_items).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        #endregion
        #endregion

        #region Save
        public void Save()
        {
            string connectionStringName = (string)Settings.Instance.GetSetting(SettingsType.ConnectionStringName, this.GetType());
            string connstr = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
            using (DbConnection cn = ((IDatabaseProvider)Settings.Instance.GetSetting(SettingsType.Provider, this.GetType())).GetConnection(connstr))
            {
                cn.Open();
                using (DbTransaction trans = cn.BeginTransaction())
                {
                    try
                    {
                        // Do the persistence
                        Persist(trans, PersistType.Downward);
                    }
                    catch (Exception innerException)
                    {
                        try
                        {
                            // Undo all persistence calls.
                            trans.Rollback();

                            // Call the error event to allow for automatic logging.
                            OnError(innerException);
                        }
                        catch (Exception outerException)
                        {
                            // if an exception is caught then something in the OnError message when wrong.
                            // Throw a new Exception to throw up both the original error and the new one from the error handler!
                            throw new EntityErrorException(Resources.EntityErrorMessage, outerException, innerException);
                        }

                        // the error handler was fine so throw up the
                        // original exception.
                        throw innerException;
                    }

                    //No errors detected. Commit persist transaction.
                    trans.Commit();
                }
            }
        }

        private PersistenceGuard _persistenceGuard = null;

        public void Persist(DbTransaction transaction, PersistType persistType)
        {
            if (_persistenceGuard == null || _persistenceGuard.Persisting == false)
            {
                using (_persistenceGuard = new PersistenceGuard())
                {
                    OnPersisting(transaction);
                    
                    //Persist deleted entities
                    OnDeleting(transaction);
                    foreach (T t in _deletedList)
                        t.Persist(transaction, persistType);
                    _deletedList.Clear();
                    OnDeleted(transaction);

                    //Persist dirty entities
                    foreach (T t in _items)
                        t.Persist(transaction, persistType);

                    OnPersisted(transaction);
                }
            }
        }
        #endregion

        #region IBusinessObject Members

        public void Delete()
        {
            Clear();
        }

        public bool IsNew
        {
            get
            {
                bool isnew = false;
                foreach (T c in _items)
                {
                    if (c.IsNew)
                    {
                        isnew = true;
                        break;
                    }
                }
                return isnew;
            }
        }

        public bool IsDeleted
        {
            get { return _items.Count == 0 && _deletedList.Count > 0; }
        }

        #endregion

        #region IList<C> Members

        public int IndexOf(T item)
        {
            return _items.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            _items.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            _items.RemoveAt(index);
        }

        #endregion

        #region Error
        public event EventHandler<EventArgs<Exception>> Error;
        protected virtual void OnError(Exception ex)
        {
            if (Error != null) Error(this, new EventArgs<Exception>(ex));
        }
        #endregion

        #region IEntityCollection Members

        EntityBase IEntityCollection.this[int index]
        {
            get
            {
                return this[index];
            }
            set
            {
                if (!(value is T))
                    throw new ArgumentException("Must be of type" + typeof(T).ToString(), "value");

                this[index] = (T)value;
            }
        }

        int IEntityCollection.IndexOf(EntityBase entity)
        {
            if (!(entity is T))
                throw new ArgumentException("Must be of type" + typeof(T).ToString(), "value");

            return this.IndexOf((T)entity);
        }

        void IEntityCollection.Remove(EntityBase entity)
        {
            if (!(entity is T))
                throw new ArgumentException("Must be of type" + typeof(T).ToString(), "value");

            this.Remove((T)entity);
        }

        void IEntityCollection.Add(EntityBase entity)
        {
            if (!(entity is T))
                throw new ArgumentException("Must be of type" + typeof(T).ToString(), "value");

            this.Add((T)entity);
        }

        #endregion

        #region IPersistable Members
        public event EventHandler<PersistEventArgs> Persisting;
        protected virtual void OnPersisting(DbTransaction transaction)
        {
            if (Persisting != null)
                Persisting(this, new PersistEventArgs(transaction));
        }

        public event EventHandler<PersistEventArgs> Persisted;
        protected virtual void OnPersisted(DbTransaction transaction)
        {
            if (Persisted != null)
                Persisted(this, new PersistEventArgs(transaction));
        }
        #endregion

        #region IDeletable Members
        public event EventHandler<DeleteEventArgs> Deleting;
        protected virtual void OnDeleting(DbTransaction transaction)
        {
            if (Deleting != null)
                Deleting(this, new DeleteEventArgs(transaction));
        }

        public event EventHandler<DeleteEventArgs> Deleted;
        protected virtual void OnDeleted(DbTransaction transaction)
        {
            if (Deleted != null)
                Deleted(this, new DeleteEventArgs(transaction));
        }
        #endregion

        #region IEnumerable<T> Members

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            foreach (T t in this._items)
            {
                yield return t;
            }

            List<T> list;
        }

        #endregion

        #region List<T> wrappers
        #region Sort
        public void Sort()
        {
            _items.Sort();
        }

        public void Sort(Comparison<T> comparison)
        {
            _items.Sort(comparison);
        }

        public void Sort(IComparer<T> comparer)
        {
            _items.Sort(comparer);
        }

        public void Sort(int index, int count, IComparer<T> comparer)
        {
            _items.Sort(index, count, comparer);
        }
        #endregion

        #region ToArray
        public T[] ToArray()
        {
            return _items.ToArray();
        }
        #endregion 

        #region Find
        public T Find(Predicate<T> match)
        {
            return _items.Find(match);
        }

        public T[] FindAll(Predicate<T> match)
        {
            return _items.FindAll(match).ToArray();
        }
        #endregion
        #endregion
    }
}
